package com.kai.service.util;

import org.springframework.data.redis.core.RedisTemplate;

import java.util.Objects;
import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * redis分布式锁 用于数据要求比较准确的场景
 */
public class RedisLockUtil {
    private static final String LOCKED = "TRUE";
    private static final int EXPIRE = 180; // s
    private static final long DEFAULT_TIMEOUT = 2000L; // ms

    private static final String NX = "NX"; // 不存在时操作
    private static final String XX = "XX"; // 存在时操作

    private static final String EX = "EX"; // 过期时间为秒
    private static final String PX = "PX"; // 过期时间为毫秒

    public static final Random random = new Random();

    private static final long ONE_MILLI_NANOS = 1000000L;

    private static final RedisTemplate<Object, Object> REDIS_TEMPLATE;

    static {
        REDIS_TEMPLATE = SpringContextUtil.getBean("redisTemplate");
    }

    /**
     * 执行同步操作
     *
     * @param operator
     * @param lockCacheKey
     * @param milliTimeout
     * @return
     * @param <T>
     * @throws Exception
     */
    public static <T> T executeSyncOperate(ExecuteOperator<T> operator, String lockCacheKey, long milliTimeout) throws Exception {
        T result;
        boolean locked = false;
        long startNanos = System.nanoTime();
        long nanoTimeOut = milliTimeout * ONE_MILLI_NANOS;
        try {
            // 默认时间内不执行
            while (System.nanoTime() - startNanos < nanoTimeOut) {
                locked = Boolean.TRUE.equals(REDIS_TEMPLATE.opsForValue().setIfAbsent(lockCacheKey, LOCKED, EXPIRE, TimeUnit.SECONDS));
                if (locked) {
                    break;
                }
                Thread.sleep(100L, random.nextInt(500));
            }
            // 执行逻辑
            result = operator.executeInvokeLogic(locked);
        } finally {
            if (locked) {
                releaseRedisLock(lockCacheKey);
            } else {
                checkIsDeadlock(lockCacheKey);
            }

        }

        return result;
    }

    /**
     * 执行同步操作
     *
     * @param operator
     * @param lockCacheKey
     * @return
     * @param <T>
     * @throws Exception
     */
    public static <T> T executeSyncOperate(ExecuteOperator<T> operator, String lockCacheKey) throws Exception {
        return executeSyncOperate(operator, lockCacheKey, DEFAULT_TIMEOUT);
    }

    /**
     * 释放锁
     *
     * @param cacheKey
     */
    public static void releaseRedisLock(String cacheKey) {
        REDIS_TEMPLATE.delete(cacheKey);
    }

    /**
     * 检查死锁
     *
     * @param lockCacheKey
     */
    private static void checkIsDeadlock(String lockCacheKey) {
        if (Objects.equals(REDIS_TEMPLATE.getExpire(lockCacheKey), -1L)) {
            REDIS_TEMPLATE.expire(lockCacheKey, EXPIRE, TimeUnit.SECONDS);
        }

    }

    /**
     * 执行操作
     *
     * @param <T>
     */
    public interface ExecuteOperator<T> {
        T executeInvokeLogic(boolean locked) throws Exception;
    }

}